package com.alibaba.dubbo.remoting.http.servlet;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.dubbo.common.configure.ExtensionConfiguratorUtils;
import com.alibaba.dubbo.common.utils.MimeTypeUtils;

public class StaticResourceServlet extends HttpServlet {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	static StaticResourceServlet INSTANCE;
	
	public static StaticResourceServlet getInstance() {
    	return INSTANCE;
    }
	
	static final String KEY_HTTP_STATIC_RESOURCE_CACHE_THRESHOLD_BYTES = "http.static-resource.cache.threshold.bytes";
	static final String KEY_HTTP_STATIC_RESOURCE_CACHE_THRESHOLD_KBYTES = "http.static-resource.cache.threshold.kbytes";
	static final String KEY_HTTP_STATIC_RESOURCE_CACHE_THRESHOLD_MBYTES = "http.static-resource.cache.threshold.mbytes";
    int HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES = 10 * 1024*1024;
    public StaticResourceServlet() {
    	StaticResourceServlet.INSTANCE = this;
    	ExtensionConfiguratorUtils utils = ExtensionConfiguratorUtils.getInstance();
    	HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES = utils.getIntValue(new String[]{KEY_HTTP_STATIC_RESOURCE_CACHE_THRESHOLD_BYTES}, HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES);
    	int v = utils.getIntValue(new String[]{KEY_HTTP_STATIC_RESOURCE_CACHE_THRESHOLD_KBYTES}, HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES/1024);
    	HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES = v*1024;
    	v = utils.getIntValue(new String[]{KEY_HTTP_STATIC_RESOURCE_CACHE_THRESHOLD_MBYTES}, v/1024);
    	HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES = v*1024*1024;
    }
    
    /**
     * fix to support range
     * @param req
     * @param resp
     * @param resource
     * @throws IOException
     */
    void getFromCache(HttpServletRequest req, HttpServletResponse resp, ResourceCache resource) throws IOException{
    	if(resource != null){
    		int from = -1;
    		int to = -1;
    		int contentLength = (int)resource.length;
    		String rangeBytes = "";
    		if(req.getHeader("Range") != null){
    			rangeBytes = req.getHeader("Range").replaceAll("bytes=", ""); 
    			if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {//bytes=969998336-  
                    rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));  
                    from = Integer.parseInt(rangeBytes.trim());  
                    contentLength = (int) (resource.length - from + 1);//客户端请求的是 969998336 之后的字节  
                } else {//bytes=1275856879-1275877358  
                    String temp0 = rangeBytes.substring(0,rangeBytes.indexOf('-'));  
                    String temp2 = rangeBytes.substring(rangeBytes.indexOf('-') + 1, rangeBytes.length());  
                    from = Integer.parseInt(temp0.trim());//bytes=1275856879-1275877358，从第 1275856879 个字节开始下载  
                    to = Integer.parseInt(temp2);//bytes=1275856879-1275877358，到第 1275877358 个字节结束  
                    contentLength = to - from + 1;//客户端请求的是 1275856879-1275877358  之间的字节  
                }  
    		}
    		
			resp.reset();
			resp.setContentType(resource.mime);
			resp.setHeader("Accept-Ranges", "bytes");
			resp.addHeader("Content-Disposition", "attachment; filename=" + resource.filename);  
			resp.setContentLength(contentLength);
			
			OutputStream toClient = new BufferedOutputStream(resp.getOutputStream());
    		if(from == -1){
				resp.setStatus(HttpServletResponse.SC_OK);
				toClient.write(resource.data);
    		}else{
				resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
				if(to < 0){
					String contentRange = new StringBuffer("bytes ").append(new Integer(from).toString()).append("-").append(new Long(resource.length - 1).toString()).append("/").append(new Long(resource.length).toString()).toString();  
					resp.setHeader("Content-Range", contentRange); 
				}else{
					String contentRange = rangeBytes + "/" + new Long(resource.length).toString();  
                    resp.setHeader("Content-Range", contentRange);  
				}
				toClient.write(resource.data, from, contentLength);
    		}
			toClient.flush();
			toClient.close();
		}
    }
    
    /**
     * fix to support range
     * @param req
     * @param resp
     * @param resource
     * @throws IOException
     */
    void getFromFile(HttpServletRequest req, HttpServletResponse resp, File resource) throws IOException{
    	if(resource != null){
    		int from = -1;
    		int to = -1;
    		int contentLength = (int)resource.length();
    		String rangeBytes = "";
    		if(req.getHeader("Range") != null){
    			rangeBytes = req.getHeader("Range").replaceAll("bytes=", ""); 
    			if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {//bytes=969998336-  
                    rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));  
                    from = Integer.parseInt(rangeBytes.trim());  
                    contentLength = (int) (resource.length() - from + 1);//客户端请求的是 969998336 之后的字节  
                } else {//bytes=1275856879-1275877358  
                    String temp0 = rangeBytes.substring(0,rangeBytes.indexOf('-'));  
                    String temp2 = rangeBytes.substring(rangeBytes.indexOf('-') + 1, rangeBytes.length());  
                    from = Integer.parseInt(temp0.trim());//bytes=1275856879-1275877358，从第 1275856879 个字节开始下载  
                    to = Integer.parseInt(temp2);//bytes=1275856879-1275877358，到第 1275877358 个字节结束  
                    contentLength = to - from + 1;//客户端请求的是 1275856879-1275877358  之间的字节  
                }  
    		}
    		
			resp.reset();
			resp.setContentType(MimeTypeUtils.getMimeType(resource));
			resp.setHeader("Accept-Ranges", "bytes");
			resp.addHeader("Content-Disposition", "attachment; filename=" + resource.getName());  
			resp.setContentLength(contentLength);
			
			OutputStream toClient = new BufferedOutputStream(resp.getOutputStream());
			FileInputStream is = new FileInputStream(resource);
    		if(from == -1){
				resp.setStatus(HttpServletResponse.SC_OK);
				byte [] data = new byte[32*1024];
				int len = 0;
				while((len = is.read(data, 0, data.length)) != -1){
					toClient.write(data, 0,  len);
				}
    		}else{
				resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
				if(to < 0){
					String contentRange = new StringBuffer("bytes ")
						.append(new Integer(from).toString()).append("-")
						.append(new Long(resource.length() - 1).toString()).append("/").append(new Long(resource.length()).toString()).toString();  
					resp.setHeader("Content-Range", contentRange); 
				}else{
					String contentRange = rangeBytes + "/" + new Long(resource.length()).toString();  
                    resp.setHeader("Content-Range", contentRange);  
				}
				is.skip(from);
				int total = contentLength;
				byte [] data = new byte[32*1024];
				int len = 0;
				while(total > 0){
					int r = Math.min(data.length, total);
					len = is.read(data, 0, r);
					if(len == -1) break;
					toClient.write(data, 0,  len);
					total -= len;
				}
    		}
			toClient.flush();
			toClient.close();
			is.close();
		}
    }

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		String path = req.getRequestURI();
		if(cache.containsKey(path)){
			WeakReference<ResourceCache> ref = cache.get(path);
			if(ref != null && ref.get() != null){
				ResourceCache resource  = ref.get();
				File file = new File(resource.file);
				//文件被删除，长度不匹配，修改日期不匹配，则认为过期
				if(!file.exists() || !file.isFile() || file.length() != resource.length
						|| file.lastModified() != resource.time){
					resource = null;
				}
				
				if(resource != null){
					getFromCache(req, resp, resource);
					return;
				}
			}
			cache.remove(path);
		}
		
		for(Map.Entry<String, String> entry : mapping.entrySet()){
			String vpath = entry.getKey();
			String rpath = entry.getValue();
			if(path.startsWith(vpath)){
				//get the real path
				String x = path.substring(vpath.length());
				if(!File.separator.equals("/")){
					x = x.replace("/", File.separator);
				}
				rpath = rpath + x;
				File file = new File(rpath);
				if(file.exists() == false || file.isFile() == false) break;
				
				//less 10M
				if(file.length() < HTTP_STATIC_RESOURCE_RANGE_CACHE_THRESHOLD_BYTES){
					FileInputStream is = new FileInputStream(file);
					byte []data = new byte[(int) file.length()];
					is.close();
					is = null;
					
					ResourceCache resource = new ResourceCache();
					resource.time = file.lastModified();
					resource.length = file.length();
					resource.file = rpath;
					resource.filename = file.getName();
					resource.data = data;
					resource.mime = MimeTypeUtils.getMimeType(file);
					cache.put(path, new WeakReference<ResourceCache>(resource));
					getFromCache(req, resp, resource);
				}else{
					getFromFile(req, resp, file);
				}
				return;
			}
		}
		
		resp.sendError(HttpServletResponse.SC_NOT_FOUND, null);
	}
	
	static class ResourceCache{
		String file;
		String filename;
		long time;
		long length;
		String mime;
		byte [] data;
	}
	
	private static final Map<String, String> mapping = new HashMap<String, String>();
	private static final Map<String, WeakReference<ResourceCache>> cache = new ConcurrentHashMap<String, WeakReference<ResourceCache>>();

	/**
	 *  add mapping for virtual path to real path
	 * @param vpath virtual path
	 * @param rpath real path
	 */
	static public void addServletMapping(String vpath, String rpath){
		mapping.put(vpath, rpath);
	}
}
